package spring.annotations;


import mybatis.plugins.annotations.Mapper;
import org.apache.commons.lang3.StringUtils;
import spring.dao.factory.FactoryBean;
import spring.factory.SingletonBeanRegistry;
import utils.ClassUtils;
import utils.ProxyUtils;
import utils.ReflectUtils;
import utils.ResourceUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class AnnotationInitListener {

    private final Set<Class<?>> mapperScanSet = new HashSet<>(10);
    private final Properties properties = new Properties();
    private final Map<String, Method> pendingMethods = new ConcurrentHashMap<>(10);
    private Object listenerInstance;
    private SingletonBeanRegistry singletonBeanRegistry;

    public void init(SingletonBeanRegistry singletonBeanRegistry, Class<?> listenerClass) {
        this.singletonBeanRegistry = singletonBeanRegistry;
        try {
            this.listenerInstance = listenerClass.getConstructor().newInstance();
            parseMapperScan(listenerClass); // 执行包扫描
            parseEnableWebMvc(listenerClass);   // 检查当前项目是否以MVC形式启动
            parseProperties(listenerClass);
            parseValue(listenerClass);
            parseBean(listenerClass);
            parseComponent();
            parseService();
            parseTransactionManagement(listenerClass);
        } catch (Exception e) {
            e.printStackTrace();
            throw new AnnotationException("解析注解信息出现错误", e);
        }
    }

    public void parseMapperScan(Class<?> listenerClass) {
        MapperScan mapperScan = listenerClass.getAnnotation(MapperScan.class);
        String[] values = mapperScan.value();
        for (String value : values) {
            this.mapperScanSet.addAll(ClassUtils.getClasses(value.trim()));
        }
    }

    public void parseEnableWebMvc(Class<?> listenerClass) {
        EnableWebMvc enableWebMvc = listenerClass.getAnnotation(EnableWebMvc.class);
        if (Objects.isNull(enableWebMvc)) {
            throw new AnnotationException("@EnableWebMvc注解未添加，项目无法以WebMvc形式启动");
        }
    }

    public void parseProperties(Class<?> listenerClass) throws IOException {
        PropertySource propertySource = listenerClass.getAnnotation(PropertySource.class);
        String[] values = propertySource.value();
        for (String value : values) {
            value = value.trim().replaceAll("classpath:", "/");
            InputStream is = ResourceUtils.parseConfig(listenerClass, value);
            this.properties.load(is);
            is.close();
        }
    }

    private void parseBean(Class<?> listenerClass) throws InvocationTargetException, IllegalAccessException {
        Method[] methods = listenerClass.getMethods();
        for (Method method : methods) {
            Bean beanAnnotation = method.getAnnotation(Bean.class);
            if (Objects.isNull(beanAnnotation)) {
                continue;
            }
            String id = beanAnnotation.value();
            if (StringUtils.isBlank(id)) {
                id = method.getName();
            }
            if (singletonBeanRegistry.containsSingleton("id")) {
                throw new AnnotationException("当前存在 bean 名称重复的实例，请检查您的编码。方法路径：" + method.toGenericString());
            }
            Class<?>[] paramClasses = method.getParameterTypes();
            Object[] paramObjects = getParamObjects(paramClasses);
            if (Objects.isNull(paramObjects)) {
                pendingMethods.put(id, method);
            } else {
                Object beanInstance = method.invoke(listenerInstance, paramObjects);
                singletonBeanRegistry.registerSingleton(id, beanInstance);
            }
        }
        for (Map.Entry<String, Method> entry : pendingMethods.entrySet()) {
            String id = entry.getKey();
            Method method = entry.getValue();
            Class<?>[] paramClasses = method.getParameterTypes();
            Object[] paramObjects = getParamObjects(paramClasses);
            if (Objects.isNull(paramObjects)) {
                throw new AnnotationException("当前 bean 构造的入参类型没有正确的参数值可以匹配。方法路径：" + method.toGenericString());
            } else {
                Object beanInstance = method.invoke(listenerInstance, paramObjects);
                singletonBeanRegistry.registerSingleton(id, beanInstance);
            }
        }
        pendingMethods.clear();
    }

    private Object[] getParamObjects(Class<?>[] paramClasses) {
        Object[] paramObjects = new Object[paramClasses.length];
        for (int i = 0; i < paramClasses.length; i++) {
            Class<?> paramClass = paramClasses[i];
            String beanId = ReflectUtils.convertName(paramClass.getName());
            Object paramObject = singletonBeanRegistry.getSingleton(beanId);
            if (Objects.isNull(paramObject)) {
                paramObjects = null;
                break;
            }
            paramObjects[i] = paramObject;
        }
        return paramObjects;
    }

    public void parseValue(Class<?> listenerClass) throws IllegalAccessException {
        Field[] fields = listenerClass.getDeclaredFields();
        for (Field field : fields) {
            Value valueAnnotation = field.getAnnotation(Value.class);
            if (Objects.isNull(valueAnnotation)) {
                continue;
            }
            String key = valueAnnotation.value()
                    .replaceAll("#\\{", "")
                    .replaceAll("}", "");
            Object value = this.properties.get(key);
            field.setAccessible(true);
            field.set(this.listenerInstance, value);
        }
    }

    public void parseTransactionManagement(Class<?> listenerClass) throws NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, InstantiationException, ClassNotFoundException {
        EnableTransactionManagement etm = listenerClass.getAnnotation(EnableTransactionManagement.class);
        if (Objects.isNull(etm)) {
            return;
        }
        Import importAnnotation = EnableTransactionManagement.class.getAnnotation(Import.class);
        Class<?> cls = importAnnotation.value()[0];
        ImportSelector selector = (ImportSelector) cls.getConstructor().newInstance();
        String[] registrarClasses = selector.selectImports();
        for (String clsName : registrarClasses) {
            Class<?> registrarCls = Class.forName(clsName);
            ImportBeanDefinitionRegistrar registrarObject = (ImportBeanDefinitionRegistrar) registrarCls.getConstructor().newInstance();
            registrarObject.registerBeanDefinitions(this);
        }
    }

    public void parseService() {
        mapperScanSet.stream()
                .filter(o -> Objects.nonNull(o.getAnnotation(Service.class)))
                .forEach(beanCls -> {
                    String id = beanCls.getAnnotation(Service.class).value();
                    if (StringUtils.isBlank(id)) {
                        id = ReflectUtils.convertName(beanCls.getInterfaces()[0].getSimpleName());
                    }
                    try {
                        Object serviceObject = createBeanObject(beanCls);
                        // 创建当前 bean 的实例代理对象
                        Object beanInstanceProxy = ProxyUtils.createProxyObject(serviceObject);
                        // 保存到单例池
                        singletonBeanRegistry.registerSingleton(id, beanInstanceProxy);
                    } catch (Exception e) {
                        e.printStackTrace();
                        throw new AnnotationException(beanCls.getName() + "实例化失败。");
                    }
                });
    }

    private void parseComponent() throws Exception {
        for (Class<?> beanCls : mapperScanSet) {
            Component componentAnnotation = beanCls.getAnnotation(Component.class);
            if (Objects.isNull(componentAnnotation)) {
                continue;
            }
            String id = componentAnnotation.value();
            if (StringUtils.isBlank(id)) {
                id = ReflectUtils.convertName(beanCls.getSimpleName());
            }
            Mapper mapper = beanCls.getAnnotation(Mapper.class);
            if (Objects.nonNull(mapper)) {
                parseMapper(beanCls);
            } else {
                // 创建 bean 对象
                Object beanInstance = createBeanObject(beanCls);
                // 创建当前 bean 的实例代理对象
                Object beanInstanceProxy = ProxyUtils.createProxyObject(beanInstance);
                // 保存到单例池
                singletonBeanRegistry.registerSingleton(id, beanInstanceProxy);
            }
        }
    }

    private Object createBeanObject(Class<?> beanCls) throws Exception {
        Object beanInstance = beanCls.getConstructor().newInstance();
        Field[] fields = beanCls.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            Autowired autowired = field.getAnnotation(Autowired.class);
            if (Objects.isNull(autowired)) {
                continue;
            }
            Object obj = singletonBeanRegistry.getSingleton(field.getName());
            if (obj instanceof FactoryBean) {
                field.set(beanInstance, ((FactoryBean<?>) obj).getObject());
            } else {
                field.set(beanInstance, obj);
            }
        }
        return beanInstance;
    }

    private void parseMapper(Class<?> mapperInterface) throws Exception {
        Import importAnnotation = Mapper.class.getAnnotation(Import.class);
        Class<?> selectorClass = importAnnotation.value()[0];
        ImportSelector selector = (ImportSelector) selectorClass.getConstructor().newInstance();
        String registrarClsName = selector.selectImports()[0];
        Class<?> registrarClass = Class.forName(registrarClsName);
        ImportBeanDefinitionRegistrar registrar =
                (ImportBeanDefinitionRegistrar) registrarClass.getConstructor().newInstance();
        Query query = new FactoryQuery(singletonBeanRegistry);
        Object factoryBean = registrar.registerBeanDefinitions(query, mapperInterface.getName());
        String beanId = ReflectUtils.convertName(mapperInterface.getSimpleName());
        singletonBeanRegistry.registerSingleton(beanId, factoryBean);
    }

    private Object getBean(Object id) {
        return singletonBeanRegistry.getSingleton(id.toString());
    }

    public Set<Class<?>> getMapperScanSet() {
        return mapperScanSet;
    }

    public SingletonBeanRegistry getSingletonBeanRegistry() {
        return singletonBeanRegistry;
    }

    public static class FactoryQuery implements Query {

        private final SingletonBeanRegistry registry;

        public FactoryQuery(SingletonBeanRegistry registry) {
            this.registry = registry;
        }

        @Override
        public Object query(String id) {
            return registry.getSingleton(id);
        }
    }
}
